package jp.dodododo.aop.interceptors;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import jp.dodododo.aop.HasTargetClass;
import jp.dodododo.aop.annotation.Enhance;

import org.aopalliance.intercept.Interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

@Enhance(false)
class NestedMethodInvocation implements MethodInvocation, HasTargetClass {
	private final MethodInvocation parent;

	private final MethodInterceptor[] interceptors;

	private int interceptorsIndex;

	public NestedMethodInvocation(MethodInvocation parent, MethodInterceptor... interceptors) {
		this.parent = parent;
		this.interceptors = interceptors;
	}

	public NestedMethodInvocation(MethodInvocation parent, List<? extends Interceptor> interceptors) {
		this(parent, toMethodInterceptors(interceptors));
	}

	private static MethodInterceptor[] toMethodInterceptors(List<? extends Interceptor> interceptors) {
		List<MethodInterceptor> ret = new ArrayList<MethodInterceptor>();
		for (Iterator<? extends Interceptor> iter = interceptors.iterator(); iter.hasNext();) {
			Interceptor interceptor = iter.next();
			if (interceptor instanceof MethodInterceptor) {
				ret.add((MethodInterceptor) interceptor);
			}
		}
		return ret.toArray(new MethodInterceptor[ret.size()]);
	}

	public Object proceed() throws Throwable {
		if (interceptorsIndex < interceptors.length) {
			return interceptors[interceptorsIndex++].invoke(this);
		}
		return parent.proceed();
	}

	public Object getThis() {
		return parent.getThis();
	}

	public Object[] getArguments() {
		return parent.getArguments();
	}

	public Method getMethod() {
		return parent.getMethod();
	}

	public AccessibleObject getStaticPart() {
		return parent.getStaticPart();
	}

	public Class<?> getTargetClass() {
		if (parent instanceof HasTargetClass) {
			return ((HasTargetClass) parent).getTargetClass();
		}
		Object thisObject = getThis();
		if (thisObject != null) {
			return thisObject.getClass();
		}
		return getMethod().getDeclaringClass();
	}

	@Override
	public boolean equals(Object obj) {
		return parent.equals(obj);
	}

	@Override
	public int hashCode() {
		return parent.hashCode();
	}
}
